﻿using Framework.Core.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Volo.Abp.AspNetCore.Mvc;

namespace Framework.AspNetCore.Extensons
{
	/// <summary>
	/// Swagger 扩展
	/// </summary>
	public static class SwaggerServiceCollectionExtensons
	{
		public static IApplicationBuilder UseAbpSwagger(this IApplicationBuilder app, params SwaggerModel[] swaggerModels)
		{
			var mvcOptions = app.ApplicationServices.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().Value;

			app.UseSwagger();
			app.UseSwaggerUI(c =>
			{
				foreach (var setting in mvcOptions.ConventionalControllers.ConventionalControllerSettings)
				{
					c.SwaggerEndpoint($"/swagger/{setting.RemoteServiceName}/swagger.json", setting.RemoteServiceName);
				}
				if (mvcOptions.ConventionalControllers.ConventionalControllerSettings.Count == 0 && swaggerModels.Length == 0)
				{
					c.SwaggerEndpoint("/swagger/v1/swagger.json", "AbpDemo.Framework");
				}
				else
				{
					foreach (var k in swaggerModels)
					{
						c.SwaggerEndpoint(k.Url, k.Name);
					}
				}

			});

			return app;
		}

		public static IServiceCollection AddSwaggerGen<Program>(this IServiceCollection services, Action<SwaggerGenOptions>? action = null)
		{
			var serviceProvider = services.BuildServiceProvider();
			var mvcOptions = serviceProvider.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>();

			var mvcSettings = mvcOptions.Value.ConventionalControllers.ConventionalControllerSettings.DistinctBy(x => x.RemoteServiceName);


			services.AddAbpSwaggerGen(options =>
			{
				if (action is not null)
				{
					action.Invoke(options);
				}

				// 配置分组,还需要去重,支持重写,如果外部传入后，将以外部为准
				foreach (var setting in mvcSettings.OrderBy(x => x.RemoteServiceName))
				{
					if (!options.SwaggerGeneratorOptions.SwaggerDocs.ContainsKey(setting.RemoteServiceName))
					{
						options.SwaggerDoc(setting.RemoteServiceName, new OpenApiInfo { Title = setting.RemoteServiceName, Version = "v1" });
					}
				}

				// 根据分组名称过滤 API 文档
				options.DocInclusionPredicate((docName, apiDesc) =>
				{
					if (apiDesc.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
					{
						var settingOrNull = mvcSettings.Where(x => x.Assembly == controllerActionDescriptor.ControllerTypeInfo.Assembly).FirstOrDefault();
						if (settingOrNull is not null)
						{
							return docName == settingOrNull.RemoteServiceName;
						}
					}
					return false;
				});

				// 配置 Swagger SchemaIds
				ConfigureSchemaIds(options);

				var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location);
				if (basePath is not null)
				{
					foreach (var item in Directory.GetFiles(basePath, "*.xml"))
					{
						options.IncludeXmlComments(item, true);
					}
				}

				options.AddSecurityDefinition("JwtBearer", new OpenApiSecurityScheme()
				{
					Description = "直接输入Token即可",
					Name = "Authorization",
					In = ParameterLocation.Header,
					Type = SecuritySchemeType.Http,
					Scheme = "bearer"
				});
				var scheme = new OpenApiSecurityScheme()
				{
					Reference = new OpenApiReference() { Type = ReferenceType.SecurityScheme, Id = "JwtBearer" }
				};
				options.AddSecurityRequirement(new OpenApiSecurityRequirement()
				{
					[scheme] = new string[0]
				});

				options.SchemaFilter<EnumSchemaFilter>();

				// 修复 editor.swagger.io 生成不能正常处理 C# object 类型问题
				options.SchemaFilter<AnySchemaFilter>();

			});
			return services;
		}

		/// <summary>
		/// 配置 Swagger SchemaIds
		/// </summary>
		/// <param name="swaggerGenOptions">Swagger 生成器配置</param>
		private static void ConfigureSchemaIds(SwaggerGenOptions swaggerGenOptions)
		{
			// 本地函数
			static string DefaultSchemaIdSelector(Type modelType)
			{
				var modelName = modelType.Name;

				// 处理泛型类型问题
				if (modelType.IsConstructedGenericType)
				{
					var prefix = modelType.GetGenericArguments()
						.Select(genericArg => DefaultSchemaIdSelector(genericArg))
						.Aggregate((previous, current) => previous + current);

					// 通过 _ 拼接多个泛型
					modelName = modelName.Split('`').First() + "_" + prefix;
				}

				return modelName;
			}

			// 调用本地函数
			swaggerGenOptions.CustomSchemaIds(modelType => DefaultSchemaIdSelector(modelType));
		}

		/// <summary>
		/// 修正 规范化文档 object schema，统一显示为 any
		/// </summary>
		/// <remarks>相关 issue：https://github.com/swagger-api/swagger-codegen-generators/issues/692 </remarks>
		public class AnySchemaFilter : ISchemaFilter
		{
			/// <summary>
			/// 实现过滤器方法
			/// </summary>
			/// <param name="model"></param>
			/// <param name="context"></param>
			public void Apply(OpenApiSchema model, SchemaFilterContext context)
			{

				var type = context.Type;
				if (type == typeof(object))
				{
					model.AdditionalPropertiesAllowed = false;
				}
			}
		}

		/// <summary>
		/// Swagger文档枚举字段显示枚举属性和枚举值,以及枚举描述
		/// </summary>
		public class EnumSchemaFilter : ISchemaFilter
		{
			/// <summary>
			/// 中文正则表达式
			/// </summary>
			private const string CHINESE_PATTERN = @"[\u4e00-\u9fa5]";

			/// <summary>
			/// 实现接口
			/// </summary>
			/// <param name="model"></param>
			/// <param name="context"></param>

			public void Apply(OpenApiSchema model, SchemaFilterContext context)
			{
				var type = context.Type;

				// 排除其他程序集的枚举
				if (type.IsEnum)
				{
					model.Enum.Clear();
					var stringBuilder = new StringBuilder();
					stringBuilder.Append($"{model.Description}<br />");

					var enumValues = Enum.GetValues(type);

					bool convertToNumber;
					convertToNumber = true;

					// 包含中文情况
					if (Enum.GetNames(type).Any(v => Regex.IsMatch(v, CHINESE_PATTERN)))
					{
						convertToNumber = true;
					}

					// 获取枚举实际值类型
					Type enumValueType = type.GetField("value__").FieldType;

					foreach (object value in enumValues)
					{
						var numValue = value.ChangeType(enumValueType);

						// 获取枚举成员特性
						var fieldinfo = type.GetField(Enum.GetName(type, value));
						var descriptionAttribute = fieldinfo.GetCustomAttribute<DescriptionAttribute>(true);
						model.Enum.Add(!convertToNumber
							? new OpenApiString(value.ToString())
							: OpenApiAnyFactory.CreateFromJson($"{numValue}"));

						stringBuilder.Append($"&nbsp;{descriptionAttribute?.Description} {value} = {numValue}<br />");
					}
					model.Description = stringBuilder.ToString();

					if (!convertToNumber)
					{
						model.Type = "string";
						model.Format = null;
					}
				}
			}
		}
	}

	public class SwaggerModel
	{
		public SwaggerModel(string name)
		{
			this.Name = name;
			this.Url = "/swagger/v1/swagger.json";
		}
		public SwaggerModel(string url, string name)
		{
			this.Url = url;
			this.Name = name;
		}
		public string Url { get; set; }
		public string Name { get; set; }
	}
}
